#include <filament/Engine.h>
#include <filament/Scene.h>
#include <filament/RenderableManager.h>
#include <filament/TransformManager.h>
#include <filament/View.h>
#include <filament/Camera.h>
#include <filament/Viewport.h>
#include <filament/Color.h>
#include <filament/LightManager.h>
#include <filament/Renderer.h>
#include <filament/Material.h>
#include <filament/MaterialInstance.h>
#include <filament/VertexBuffer.h>
#include <filament/IndexBuffer.h>
#include <filament/SwapChain.h>
#include <filament/Skybox.h>

#include <utils/EntityManager.h>
#include <utils/Path.h>

#include <cmath>
#include <iostream>
#include <string>// for printing usage/help
#include <fstream>

#include <GLFW/glfw3.h>
#define GLFW_EXPOSE_NATIVE_WIN32
#include <GLFW/glfw3native.h>

#include "resources.h"

using namespace filament;
using namespace filament::math;
using namespace utils;

const int WINDOW_WIDTH = 800;
const int WINDOW_HEIGHT = 600;

static const float M_PI = 3.14159265358979323846f;

struct Vertex {
	float2 position;
	uint32_t color;
};

static const Vertex TRIANGLE_VERTICES[3] = {
	{{1, 0}, 0xffff0000u},
	{{cos(M_PI * 2 / 3), sin(M_PI * 2 / 3)}, 0xff00ff00u},
	{{cos(M_PI * 4 / 3), sin(M_PI * 4 / 3)}, 0xff0000ffu},
};

static constexpr uint16_t TRIANGLE_INDICES[3] = { 0, 1, 2 };

int _main(int argc, char* argv[]) {
	if (!glfwInit()) {
		std::cerr << "glfwInit fail!" << std::endl;
		return -1;
	}
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
	GLFWwindow* window = glfwCreateWindow(WINDOW_WIDTH, WINDOW_HEIGHT, "Filament Cube Example", nullptr, nullptr);
	if (!window) {
		glfwTerminate();
		return -1;
	}
	glfwMakeContextCurrent(window);
	const bool Vsync = true;
	glfwSwapInterval(Vsync ? 1 : 0);// setContextCurrent must be called before this

	/* ******************************************************************************/
	/* ******************************************************************************/
	/* ******************************************************************************/

	Engine* engine = Engine::create(Engine::Backend::OPENGL);

	void* nativeWindow = glfwGetWin32Window(window);
	SwapChain* swapChain = engine->createSwapChain(nativeWindow);

	Scene* scene = engine->createScene();

	View* view = engine->createView();
	view->setViewport({ 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT });
	view->setScene(scene);
	view->setPostProcessingEnabled(false);

	Skybox* skybox = Skybox::Builder().color({ 0.1, 0.125, 0.25, 1.0 }).build(*engine);// will override the ClearColor
	scene->setSkybox(skybox);
	
	static_assert(sizeof(Vertex) == 12, "Strange vertex size.");

	VertexBuffer* vb = VertexBuffer::Builder()
		.vertexCount(3)
		.bufferCount(1)
		.attribute(VertexAttribute::POSITION, 0, VertexBuffer::AttributeType::FLOAT2, 0, 12)
		.attribute(VertexAttribute::COLOR, 0, VertexBuffer::AttributeType::UBYTE4, 8, 12)
		.normalized(VertexAttribute::COLOR)
		.build(*engine);
	vb->setBufferAt(*engine, 0, VertexBuffer::BufferDescriptor(TRIANGLE_VERTICES, 36, nullptr));

	IndexBuffer* ib = IndexBuffer::Builder()
		.indexCount(3)
		.bufferType(IndexBuffer::IndexType::USHORT)
		.build(*engine);
	ib->setBuffer(*engine, IndexBuffer::BufferDescriptor(TRIANGLE_INDICES, 6, nullptr));

	Material* mat = Material::Builder()
		.package(RESOURCES_BAKEDCOLOR_DATA, RESOURCES_BAKEDCOLOR_SIZE)
		.build(*engine);

	Entity renderable = EntityManager::get().create();
	RenderableManager::Builder(1)
		.boundingBox({ { -1, -1, -1 }, { 1, 1, 1 } })
		.material(0, mat->getDefaultInstance())
		.geometry(0, RenderableManager::PrimitiveType::TRIANGLES, vb, ib, 0, 3)
		.culling(false)
		.receiveShadows(false)
		.castShadows(false)
		.build(*engine, renderable);
	scene->addEntity(renderable);

	Entity camera = EntityManager::get().create();
	Camera* cam = engine->createCamera(camera);
	view->setCamera(cam);

	Renderer* renderer = engine->createRenderer();
	renderer->setClearOptions({// will be overrided by the skybox
		.clearColor = { 0.1f, 0.9f, 0.1f, 1.0f },
		.clear = true
	});

	while (!glfwWindowShouldClose(window)) {
		glfwPollEvents();

		if (renderer->beginFrame(swapChain)) {
			renderer->render(view);
			renderer->endFrame();
		}
		glfwSwapBuffers(window);
	}

	// do clean up
	engine->destroy(skybox);
	engine->destroy(renderable);
	engine->destroy(mat);
	engine->destroy(vb);
	engine->destroy(ib);
	engine->destroyCameraComponent(camera);
	EntityManager::get().destroy(camera);

	glfwDestroyWindow(window);
	glfwTerminate();

	return 0;
}